{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "view-in-github"
   },
   "source": [
    "<a href=\"https://colab.research.google.com/github/tomasonjo/blogs/blob/master/llm/Neo4j_vector_index_%26_Langchain.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "KDuXh3VV6URH",
    "outputId": "6705030e-8bb9-4bfb-b0fd-caaa579526fd"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Requirement already satisfied: neo4j in /usr/local/lib/python3.10/dist-packages (5.11.0)\n",
      "Requirement already satisfied: langchain==0.0.270 in /usr/local/lib/python3.10/dist-packages (0.0.270)\n",
      "Requirement already satisfied: wikipedia in /usr/local/lib/python3.10/dist-packages (1.4.0)\n",
      "Requirement already satisfied: openai in /usr/local/lib/python3.10/dist-packages (0.27.9)\n",
      "Requirement already satisfied: tiktoken in /usr/local/lib/python3.10/dist-packages (0.4.0)\n",
      "Requirement already satisfied: PyYAML>=5.3 in /usr/local/lib/python3.10/dist-packages (from langchain==0.0.270) (6.0.1)\n",
      "Requirement already satisfied: SQLAlchemy<3,>=1.4 in /usr/local/lib/python3.10/dist-packages (from langchain==0.0.270) (2.0.20)\n",
      "Requirement already satisfied: aiohttp<4.0.0,>=3.8.3 in /usr/local/lib/python3.10/dist-packages (from langchain==0.0.270) (3.8.5)\n",
      "Requirement already satisfied: async-timeout<5.0.0,>=4.0.0 in /usr/local/lib/python3.10/dist-packages (from langchain==0.0.270) (4.0.3)\n",
      "Requirement already satisfied: dataclasses-json<0.6.0,>=0.5.7 in /usr/local/lib/python3.10/dist-packages (from langchain==0.0.270) (0.5.14)\n",
      "Requirement already satisfied: langsmith<0.1.0,>=0.0.21 in /usr/local/lib/python3.10/dist-packages (from langchain==0.0.270) (0.0.26)\n",
      "Requirement already satisfied: numexpr<3.0.0,>=2.8.4 in /usr/local/lib/python3.10/dist-packages (from langchain==0.0.270) (2.8.5)\n",
      "Requirement already satisfied: numpy<2,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain==0.0.270) (1.23.5)\n",
      "Requirement already satisfied: pydantic<3,>=1 in /usr/local/lib/python3.10/dist-packages (from langchain==0.0.270) (2.2.0)\n",
      "Requirement already satisfied: requests<3,>=2 in /usr/local/lib/python3.10/dist-packages (from langchain==0.0.270) (2.31.0)\n",
      "Requirement already satisfied: tenacity<9.0.0,>=8.1.0 in /usr/local/lib/python3.10/dist-packages (from langchain==0.0.270) (8.2.3)\n",
      "Requirement already satisfied: pytz in /usr/local/lib/python3.10/dist-packages (from neo4j) (2023.3)\n",
      "Requirement already satisfied: beautifulsoup4 in /usr/local/lib/python3.10/dist-packages (from wikipedia) (4.11.2)\n",
      "Requirement already satisfied: tqdm in /usr/local/lib/python3.10/dist-packages (from openai) (4.66.1)\n",
      "Requirement already satisfied: regex>=2022.1.18 in /usr/local/lib/python3.10/dist-packages (from tiktoken) (2023.6.3)\n",
      "Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain==0.0.270) (23.1.0)\n",
      "Requirement already satisfied: charset-normalizer<4.0,>=2.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain==0.0.270) (3.2.0)\n",
      "Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain==0.0.270) (6.0.4)\n",
      "Requirement already satisfied: yarl<2.0,>=1.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain==0.0.270) (1.9.2)\n",
      "Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain==0.0.270) (1.4.0)\n",
      "Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.10/dist-packages (from aiohttp<4.0.0,>=3.8.3->langchain==0.0.270) (1.3.1)\n",
      "Requirement already satisfied: marshmallow<4.0.0,>=3.18.0 in /usr/local/lib/python3.10/dist-packages (from dataclasses-json<0.6.0,>=0.5.7->langchain==0.0.270) (3.20.1)\n",
      "Requirement already satisfied: typing-inspect<1,>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from dataclasses-json<0.6.0,>=0.5.7->langchain==0.0.270) (0.9.0)\n",
      "Requirement already satisfied: annotated-types>=0.4.0 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1->langchain==0.0.270) (0.5.0)\n",
      "Requirement already satisfied: pydantic-core==2.6.0 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1->langchain==0.0.270) (2.6.0)\n",
      "Requirement already satisfied: typing-extensions>=4.6.1 in /usr/local/lib/python3.10/dist-packages (from pydantic<3,>=1->langchain==0.0.270) (4.7.1)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain==0.0.270) (3.4)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain==0.0.270) (2.0.4)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.10/dist-packages (from requests<3,>=2->langchain==0.0.270) (2023.7.22)\n",
      "Requirement already satisfied: greenlet!=0.4.17 in /usr/local/lib/python3.10/dist-packages (from SQLAlchemy<3,>=1.4->langchain==0.0.270) (2.0.2)\n",
      "Requirement already satisfied: soupsieve>1.2 in /usr/local/lib/python3.10/dist-packages (from beautifulsoup4->wikipedia) (2.4.1)\n",
      "Requirement already satisfied: packaging>=17.0 in /usr/local/lib/python3.10/dist-packages (from marshmallow<4.0.0,>=3.18.0->dataclasses-json<0.6.0,>=0.5.7->langchain==0.0.270) (23.1)\n",
      "Requirement already satisfied: mypy-extensions>=0.3.0 in /usr/local/lib/python3.10/dist-packages (from typing-inspect<1,>=0.4.0->dataclasses-json<0.6.0,>=0.5.7->langchain==0.0.270) (1.0.0)\n"
     ]
    }
   ],
   "source": [
    "!pip install neo4j langchain wikipedia openai tiktoken langchain_openai"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Deprecated, please use other examples as the LangChain has integrated neo4j support!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "A_SlDJixS0xa"
   },
   "source": [
    "# Efficient semantic search over unstructured text in Neo4j\n",
    "## Integrate the newly added vector index into LangChain to enhance your RAG applications\n",
    "\n",
    "Since the advent of ChatGPT six months ago, the technology landscape has undergone a transformative shift. ChatGPT's exceptional capacity for generalization has diminished the requirement for specialized deep learning teams and extensive training datasets to create custom NLP models. This has democratized access to a range of NLP tasks, such as summarization and information extraction, making them more readily available than ever before. However, we soon realized the limitations of ChatGPT-like models, such as knowledge date cutoff and not having access to private information. In my opinion, what followed was the second wave of generative AI transformation with the rise of Retrieval Augmented Generation (RAG) applications, where you feed relevant information to the model at query time to construct better and more accurate answers.\n",
    "\n",
    "![1_e2Z35NXHCtOom3-C5fC8Gg.png]()\n",
    "\n",
    "As mentioned, the RAG applications require a smart search tool that is able to retrieve additional information based on the user input, which allows the LLMs to produce more accurate and up-to-date answers. At first, the focus was mostly on retrieving information from unstructured text using semantic search. However, it soon became evident that a combination of structured and unstructured data is the best approach to RAG applications if you want to move beyond \"Chat with your PDF\" applications.\n",
    "\n",
    "Neo4j was and is an excellent fit for handling structured information, but it struggled a bit with semantic search due to its brute-force approach. However, the struggle is in the past as Neo4j has introduced a new vector index in version 5.11 designed to efficiently perform semantic search over unstructured text or other embedded data modalities. The newly added vector index makes Neo4j a great fit for most RAG applications as it now works great with both structured and unstructured data.\n",
    "\n",
    "In this blog post I will show you how to setup a vector index in Neo4j and integrate it into the LangChain ecosystem.\n",
    "\n",
    "## Neo4j Environment setup\n",
    "\n",
    "You need to setup a Neo4j 5.11 or greater to follow along with the examples in this blog post. The easiest way is to start a free instance on Neo4j Aura, which offers cloud instances of Neo4j database. Alternatively, you can also setup a local instance of the Neo4j database by downloading the Neo4j Desktop application and creating a local database instance.\n",
    "\n",
    "After you have instantiated the Neo4j database, you can use the LangChain library to connect to it."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "id": "zBDvwyT_6WVz"
   },
   "outputs": [],
   "source": [
    "from langchain.graphs import Neo4jGraph\n",
    "\n",
    "NEO4J_URI=\"neo4j+s://databases.neo4j.io\"\n",
    "NEO4J_USERNAME=\"neo4j\"\n",
    "NEO4J_PASSWORD=\"-\"\n",
    "\n",
    "graph = Neo4jGraph(\n",
    "    url=NEO4J_URI,\n",
    "    username=NEO4J_USERNAME,\n",
    "    password=NEO4J_PASSWORD\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "8Haa5NhCTJQX"
   },
   "source": [
    "## Setting up the Vector Index\n",
    "\n",
    "Neo4j vector index is powered by Lucene, where Lucene implements a Hierarchical Navigable Small World (HNSW) Graph to perform a approximate nearest neighbors (ANN) query over the vector space.\n",
    "\n",
    "Neo4j's implementation of the vector index is designed to index a single node property of a node label. For example, if you wanted to index nodes with the label Chunk on their node property embedding , you would use the following Cypher procedure."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Y5MtIHO3633A",
    "outputId": "17f03a0e-5b3d-4142-bdcd-87072b07a575"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[]"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.query(\"\"\"\n",
    "CALL db.index.vector.createNodeIndex(\n",
    "  'wikipedia', // index name\n",
    "  'Chunk',     // node label\n",
    "  'embedding', // node property\n",
    "   1536,       // vector size\n",
    "   'cosine'    // similarity metric\n",
    ")\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "DtpG2w8eTMt7"
   },
   "source": [
    "Along with the index name, node label, and property, you must specify the vector size (embedding dimension), and the similarity metric. We will be using OpenAI's text-embedding-ada-002 embedding model, which uses vector size 1536 to represent text in the embedding space. At the moment, only the cosine and Euclidean similarity metrics are available. OpenAI suggests using the cosine similarity metric when using their embedding model.\n",
    "\n",
    "## Populating the Vector index\n",
    "\n",
    "Neo4j is schema-less by design, which means it doesn't enforce any restrictions what goes into a node property. For example, the embedding property of the Chunk node could store integers, list of integers or even strings. Let's try this out."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "Wv3ZYrlN8HHh",
    "outputId": "ad820f4f-a7b1-4c16-b70a-39df43dbb93a"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[]"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.query(\"\"\"\n",
    "WITH [1, [1,2,3], [\"2\",\"5\"], [x in range(0, 1535) | toFloat(x)]] AS exampleValues\n",
    "UNWIND range(0, size(exampleValues) - 1) as index\n",
    "CREATE (:Chunk {embedding: exampleValues[index], index: index})\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "lMcfFqaLTQeA"
   },
   "source": [
    "This query creates a Chunknode for each element in the list and uses the element as the embeddingproperty value. For example, the first Chunk node will have the embedding property value 1, the second node [1,2,3], and so on. Neo4j doesn't enforce any rules on what you can store under node properties. However, the vector index has clear instructions about the type of values and their embedding dimension it should index.\n",
    "We can test which values were indexed by performing a vector index search."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "H8oJ8GPE8edw",
    "outputId": "9c9db94f-84bf-4c1e-8aeb-8fac1592cc7d"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{'index': 3, 'score': 1.0}]"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.query(\"\"\"\n",
    "CALL db.index.vector.queryNodes(\n",
    "  'wikipedia', // index name\n",
    "  3, // topK neighbors to return\n",
    "  [x in range(0,1535) | toFloat(x) / 2] //input vector\n",
    ")\n",
    "YIELD node, score\n",
    "RETURN node.index AS index, score\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "cxWEIm_CTayP"
   },
   "source": [
    "If you run this query, you will get only a single node returned, even though you requested the top 3 neighbors to be returned. Why is that so? The vector index only indexes property values, where the value is a list of floats with the specified size. In this example, only one embeddingproperty value had the list of floats type with the selected length 1536.\n",
    "A node is indexed by the vector index if all the following are true:\n",
    "* The node contains the configured label.\n",
    "* The node contains the configured property key.\n",
    "* The respective property value is of type LIST<FLOAT>.\n",
    "* The size() of the respective value is the same as the configured dimensionality.\n",
    "* The value is a valid vector for the configured similarity function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "B-stKD2I9NGM",
    "outputId": "8dd71044-6b3d-4295-ee25-129e90b2249a"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[]"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.query(\"\"\"\n",
    "MATCH (n) DETACH DELETE n\n",
    "\"\"\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "yGFJ8iSsTjMZ"
   },
   "source": [
    "## Integrating the vector index into the LangChain ecosystem\n",
    "Now we will implement a simple custom LangChain class that will use the Neo4j Vector index to retrieve relevant information to generate accurate and up-to-date answers. But first, we have to populate the vector index.\n",
    "\n",
    "![neo4j_vector.drawio.png]()\n",
    "\n",
    "The task will consist of the following steps:\n",
    "* Retrieve a Wikipedia article\n",
    "* Chunk the text\n",
    "* Store the text along with its vector representation in Neo4j\n",
    "* Implement a custom LangChain class to support RAG applications\n",
    "\n",
    "In this example, we will fetch only a single Wikipedia article. I have decided to use Baldur's Gate 3 page."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "id": "MnnfnP7a-Eqf"
   },
   "outputs": [],
   "source": [
    "import wikipedia\n",
    "\n",
    "bg3 = wikipedia.page(pageid=60979422)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "E9u0nUiaCHNf",
    "outputId": "c1e3f32f-70db-45fe-ee50-5be90f136817"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Baldur's Gate 3 is a role-playing video game developed and published by Larian Studios. It is the third main game in the Baldur's Gate series, which is based on the Dungeons & Dragons tabletop role-playing system. A partial version of the game was released in early access format for macOS, Windows, and the Stadia streaming service, on 6 October 2020. The game remained in early access until its full release on Windows on 3 August 2023. macOS and PlayStation 5 versions are scheduled for release on 6 September 2023. The Stadia version was cancelled following Stadia's closure.\n",
      "Baldur's Gate 3 was acclaimed by critics, who praised the gameplay, narrative, amount of content, and player choice.\n",
      "\n",
      "\n",
      "== Gameplay ==\n",
      "Baldur's Gate 3 is a role-playing video game that offers both a single-player and cooperative multiplayer element. Players can create one or more characters and form a party along with a number of pre-generated characters to explore the game's story. Optionally, players are able to take one of their characters and team up online with other players to form a party. Like previous games in the Baldur's Gate series, Baldur's Gate 3 has turn-based combat, similar to Larian's earlier games Divinity: Original Sin and Divinity: Original Sin II; all combat is based on the D&D 5th Edition rules.\n",
      "\n",
      "\n",
      "== Plot ==\n",
      "The game is set in 1492 DR, over 120 years after the events of Baldur's Gate II: Shadows of Amn and months after the events of Baldur's Gate: Descent into Avernus. It features a branching plot typical of CRPG's where the main beats of the story stay mostly the same no matter the players' choices, but with considerable variation in the details, especially concerning the numerous side quests. The player can, at the beginning of the game, either create an original character who is essentially a \"blank slate\" in terms of back story, or choose one of seven pre-made \"origin characters\".\n",
      "The first act begins with the player character finding themselves taken captive by mind flayers who have launched an invasion of Faerûn in their spelljamming ship (the D&D equivalent of a spaceship, only powered by magic instead of rocket fuel). They have implanted the protagonist with illithid tadpoles, parasites capable of enthralling and transforming them into other mind flayers; however, before transformation is complete, the ship comes under attack from githyanki warriors and their red dragons. The ship flees through multiple realms, including Avernus, the first of the Nine Hells. The protagonist is freed during the fighting and steers the damaged ship back to Faerûn, where it crashes. As the protagonist searches for a way to remove their parasite, they encounter other survivors of the wreck, all likewise implanted with tadpoles: the githyanki fighter Lae'zel, the half-elf cleric Shadowheart, the high elf vampire rogue Astarion, the human wizard Gale, the human warlock Wyll, and the tiefling barbarian Karlach.\n",
      "The protagonist eventually encounters a goblin cult which worships a new god they call \"The Absolute.\" It is soon revealed by a mysterious guardian known as the Dream Visitor that The Absolute puts the whole realm in peril. The guardian encourages the protagonist to embrace the magical Illithid powers afforded them by the parasite, assuring that they will be necessary to assail The Absolute. After breaking into the ruined Moonrise Towers, in Act 2, the player spies the masterminds behind the cult of the Absolute - the champions of the gods known as the Dead Three, Bane, Bhaal, and Myrkul. These champions have dominated an Illithid elder brain through ancient Netherese magic and are using it to control cult and mind flayers alike, planning to lead them to attack the city of Baldur's Gate. The protagonist and their party eventually manage to wrangle control of the brain from its handlers in Act 3, and the player is then, depending on choices made in the game, given a choice to either make the brain destroy itself or take control of it and take over the entire realm.\n",
      "\n",
      "\n",
      "== Development ==\n",
      "\n",
      "The original Baldur's Gate game was developed by BioWare and Black Isle Studios, and published by Interplay Entertainment in 1998. The game used a licensed version of the Dungeons & Dragons (D&D) rule set, specifically in the Forgotten Realms setting. The game's success led to a sequel, Baldur's Gate II: Shadows of Amn, and its expansion pack, as well as Icewind Dale and its sequel, and finally, Planescape: Torment. Black Isle Studios began work on a further sequel, Baldur's Gate III: The Black Hound, in 2003, but Interplay faced significant financial crisis that year and shut down Black Isle, cancelling the game. Interplay lost the license to make D&D video games to Atari in 2008. Atari later released Neverwinter Nights and its sequel, Neverwinter Nights 2. Wizards of the Coast had acquired ownership of the Dungeons & Dragons franchise as part of their purchase of TSR in 1997, and had been making updates to the core rule sets over the previous years. The new sequel from Larian Studios has no connection to the cancelled Baldur's Gate III: The Black Hound.\n",
      "The series' intellectual property (IP) had been sought after by multiple developers. This includes Brian Fargo, the  founder of both Interplay and inXile Entertainment, as well as Feargus Urquhart of Obsidian Entertainment, who were seeking the IP rights for at least a decade. Larian Studios was interested in making a sequel in the Baldur's Gate series for some time, having first approached Wizards of the Coast after their release of Divinity: Original Sin around 2014. At this time, Wizards of the Coast felt the studio was still too new to the industry to be trusted with the Baldur's Gate license. Larian then developed Divinity: Original Sin II, which was released in September 2017. Pre-release materials related to the game impressed Wizards of the Coast, so they contacted Larian to ask if they still had interest in Baldur's Gate 3. Larian accepted, and while working to wrap up the release stage of development for Divinity: Original Sin II, a small group gathered to develop the design document to present to Wizards of the Coast with their ideas for the new Baldur's Gate.\n",
      "The game is based on the 5th-edition Dungeons & Dragons rule set, though it will include tweaks and modifications that Larian found necessary in adapting it to a video game. For example, the combat system is expected to be weighed more in favor of the player than in the tabletop version, to make the game more enjoyable.Larian Studios teased Baldur's Gate 3 in the week prior to E3 2019. They formally revealed it during Google's presentation on the Stadia platform just ahead of E3, confirming its release for both Windows and Stadia. The tabletop adventure Baldur's Gate: Descent Into Avernus was published by Wizards of the Coast in September 2019, and has been described as a \"prequel\" to Baldur's Gate 3. The Descent into Avernus adventure takes place roughly 100 years after the events of Baldur's Gate II, and the story of Baldur's Gate 3 takes place immediately following the events of the Descent into Avernus tabletop module. On 5 October 2020, Larian Studios announced the game would be released for macOS in addition to Windows and Stadia.Baldur's Gate III was originally set to be released in early access on 30 September 2020. This date was later delayed to 6 October 2020. The early access version contained only the first act of the game, amounting to approximately 25 hours of content and one-fifth of the game world's map. The character creator also initially included a selection of 16 races and six classes from which to choose, with more planned for the finalized release. Additional features and content, including multiplayer functionality and more classes, were gradually added to the early access version through patches as development progressed towards the final release. Save files created during early access are not transferable to the completed game.The game remained in early access until its release in August 2023. Following Stadia's closure on 18 January 2023, the full version for Stadia was cancelled. On 23 February 2023, it was announced that Baldur's Gate 3 would be fully released on 31 August 2023 for macOS, Windows, and PlayStation 5. A later announcement amended the PC release date to 3 August, and the macOS and PlayStation 5's to 6 September. An Xbox Series X and Series S port was also confirmed by Larian to be in development, but was not officially announced as it ran into technical issues with split-screen co-op on the Series S. Larian also mentioned that the game is not a PlayStation console exclusive and might get released on Xbox should the issues get fixed. Due to the Xbox Series S's limitations and Series X's parity mandate, the Xbox Series X/S port may potentially be delayed to a 2024 release. Spike Chunsoft published the PlayStation 5 version in Japan.\n",
      "\n",
      "\n",
      "== Reception ==\n",
      "\n",
      "\n",
      "=== Critical reception ===\n",
      "The PC version of Baldur's Gate 3 received \"universal acclaim\" according to review aggregator website Metacritic.Critics praised the quality of the writing, including the detailed depiction of the Dungeons & Dragons setting of Faerûn; the intertwined storylines enabling alternate story moments and a large degree of player freedom; and the characters, particularly companions, that feel interesting and memorable. Critics also praised the production quality, including the voice acting, character and facial animations, and detailed environments. The game's implementation of the Dungeons & Dragons 5th edition ruleset was well received, although some critics were concerned that the complexity of the system could be daunting for players wholly unfamiliar with the tabletop version of the game. The technical performance and its few bugs were highlighted, especially given many vast and complex game systems and interactions. Critics were more lukewarm on the inventory system and some aspects of the user interface, such as needing to micromanage the equipment of multiple characters.Fraser Brown of PC Gamer called the game \"the new pinnacle of the [CRPG] genre\" while IGN Portugal called it a \"must-play masterpiece that sets a new bar in the RPG universe\".Upon its release, the game ignited a debate about the current state of gaming and the gaming industry. Commentators, such as Destin Legarie of IGN, highlighted statements of the developers unaffiliated with Baldur's Gate 3 who were concerned that its success may result in a perceived raising of expectations among the consumers. In response, Legarie asserted the developers should strive to meet and exceed the game's newly set standard in terms of quality and completeness, and that the developers should focus on technical stability, feature completeness and player expectations, rather than focusing on monetisation strategies and releasing unfinished products. Alex Battaglia from Eurogamer praised the game's performance on PC, and added that \"Baldur's Gate 3 launches in a polished state with no game-breaking issues, shaming the technical quality of other big AAA releases\".\n",
      "\n",
      "\n",
      "=== Sales ===\n",
      "Baldur's Gate 3 became the best-selling game on both Steam and GOG.com on the day of its early access launch. On Steam, the game had about 537,000 concurrent players on the day of release, and peaked at over 712,000 concurrent players on the 3rd day after release. On 4 August 2023, the CEO of Larian Studios, Swen Vincke, revealed that Baldur's Gate 3 had sold 2.5 million copies on PC via Steam Early Access. The sales of the game had surpassed the studio's expectations.\n",
      "\n",
      "\n",
      "== Notes ==\n",
      "\n",
      "\n",
      "== References ==\n",
      "\n",
      "\n",
      "== External links ==\n",
      "Official website\n"
     ]
    }
   ],
   "source": [
    "print(bg3.content)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "_HTsGAu3Ty-h"
   },
   "source": [
    "Next, we need to chunk and embed the text. We will split the text by section using the double newline delimiter and then use OpenAI's embedding model to represent each section with an appropriate vector."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "id": "1opRIw1oBHZQ"
   },
   "outputs": [],
   "source": [
    "import os\n",
    "from langchain.embeddings import OpenAIEmbeddings\n",
    "\n",
    "os.environ[\"OPENAI_API_KEY\"] = \"OPENAI_API_KEY\"\n",
    "\n",
    "embeddings = OpenAIEmbeddings()\n",
    "\n",
    "chunks = [{'text':el, 'embedding': embeddings.embed_query(el)} for\n",
    "                  el in bg3.content.split(\"\\n\\n\") if len(el) > 50]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "oJ0ypVg9T1ZH"
   },
   "source": [
    "Before we move on to the LangChain class, we need to import the text chunks into Neo4j."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "kOO8gvoDDK2P",
    "outputId": "1c31bae5-ad3d-4259-d8b5-de3d26653df6"
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[{\"'done'\": 'done'}]"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "graph.query(\"\"\"\n",
    "UNWIND $data AS row\n",
    "CREATE (c:Chunk {text: row.text})\n",
    "WITH c, row\n",
    "CALL db.create.setVectorProperty(c, 'embedding', row.embedding)\n",
    "YIELD node\n",
    "RETURN distinct 'done'\n",
    "\"\"\", {'data': chunks})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "WlplfAgOT4nE"
   },
   "source": [
    "One thing you can notice is that I used the db.create.setVectorProperty procedure to store the vectors to Neo4j. This procedure is used to verify that the property value is indeed a list of floats. Additionally, it has the added benefit of reducing the storage space of vector property by approximately 50%. Therefore, it is recommended always to use this procedure to store vectors to Neo4j.\n",
    "\n",
    "Now we can go and implement the custom LangChain class used to retrieve information from Neo4j vector index and use it to generate answers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "id": "rSsRMik_DUgx"
   },
   "outputs": [],
   "source": [
    "from langchain.chains.base import Chain\n",
    "from langchain.chains.llm import LLMChain\n",
    "from langchain.chat_models import ChatOpenAI\n",
    "from langchain.chains.question_answering.stuff_prompt import CHAT_PROMPT\n",
    "from langchain.callbacks.manager import CallbackManagerForChainRun\n",
    "\n",
    "from typing import Any, Dict, List\n",
    "from pydantic import Field\n",
    "\n",
    "vector_search = \"\"\"\n",
    "WITH $embedding AS e\n",
    "CALL db.index.vector.queryNodes('wikipedia',$k, e) yield node, score\n",
    "RETURN node.text AS result\n",
    "ORDER BY score DESC\n",
    "LIMIT 3\n",
    "\"\"\"\n",
    "\n",
    "class Neo4jVectorChain(Chain):\n",
    "    \"\"\"Chain for question-answering against a neo4j vector index.\"\"\"\n",
    "\n",
    "    graph: Neo4jGraph = Field(exclude=True)\n",
    "    input_key: str = \"query\"  #: :meta private:\n",
    "    output_key: str = \"result\"  #: :meta private:\n",
    "    embeddings: OpenAIEmbeddings = OpenAIEmbeddings()\n",
    "    qa_chain: LLMChain = LLMChain(llm=ChatOpenAI(temperature=0), prompt=CHAT_PROMPT)\n",
    "\n",
    "    @property\n",
    "    def input_keys(self) -> List[str]:\n",
    "        \"\"\"Return the input keys.\n",
    "        :meta private:\n",
    "        \"\"\"\n",
    "        return [self.input_key]\n",
    "\n",
    "    @property\n",
    "    def output_keys(self) -> List[str]:\n",
    "        \"\"\"Return the output keys.\n",
    "        :meta private:\n",
    "        \"\"\"\n",
    "        _output_keys = [self.output_key]\n",
    "        return _output_keys\n",
    "\n",
    "    def _call(self, inputs: Dict[str, str], run_manager, k=3) -> Dict[str, Any]:\n",
    "        \"\"\"Embed a question and do vector search.\"\"\"\n",
    "        question = inputs[self.input_key]\n",
    "        embedding = self.embeddings.embed_query(question)\n",
    "        run_manager.on_text(\n",
    "            \"Vector search embeddings:\", end=\"\\n\", verbose=self.verbose\n",
    "        )\n",
    "        run_manager.on_text(\n",
    "            embedding[:5], color=\"green\", end=\"\\n\", verbose=self.verbose\n",
    "        )\n",
    "\n",
    "        context = self.graph.query(\n",
    "            vector_search, {'embedding': embedding, 'k': 3})\n",
    "        context = [el['result'] for el in context]\n",
    "        run_manager.on_text(\n",
    "            \"Retrieved context:\", end=\"\\n\", verbose=self.verbose\n",
    "        )\n",
    "        run_manager.on_text(\n",
    "            context, color=\"green\", end=\"\\n\", verbose=self.verbose\n",
    "        )\n",
    "\n",
    "        result = self.qa_chain(\n",
    "            {\"question\": question, \"context\": context},\n",
    "        )\n",
    "        final_result = result[self.qa_chain.output_key]\n",
    "        return {self.output_key: final_result}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "fl6wN75oT8EG"
   },
   "source": [
    "I have omitted some boilerplate code to make it more readable. Essentially, when you can call the Neo4jVectorChain, the following steps are executed:\n",
    "1. Embed the question using the relevant embedding model\n",
    "2. Use the text embedding value to retrieve most similar content from the vector index\n",
    "3. Use the provided context from similar content to generate the answer\n",
    "\n",
    "We can now test our implementation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "id": "XIuN89GqD8P6"
   },
   "outputs": [],
   "source": [
    "vector_qa = Neo4jVectorChain(graph=graph, embeddings=embeddings, verbose=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 246
    },
    "id": "ymNsYxJrHEdO",
    "outputId": "bd110eb7-497e-4d84-b172-29b103b25368"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "\n",
      "\u001b[1m> Entering new Neo4jVectorChain chain...\u001b[0m\n",
      "Vector search embeddings:\n",
      "\u001b[32;1m\u001b[1;3m[0.001163442909793317, -0.03362896087993143, -0.008195404370601279, -0.002520517105697522, 0.0013487993944175268]\u001b[0m\n",
      "Retrieved context:\n",
      "\u001b[32;1m\u001b[1;3m[\"\\n== Gameplay ==\\nBaldur's Gate 3 is a role-playing video game that offers both a single-player and cooperative multiplayer element. Players can create one or more characters and form a party along with a number of pre-generated characters to explore the game's story. Optionally, players are able to take one of their characters and team up online with other players to form a party. Like previous games in the Baldur's Gate series, Baldur's Gate 3 has turn-based combat, similar to Larian's earlier games Divinity: Original Sin and Divinity: Original Sin II; all combat is based on the D&D 5th Edition rules.\", \"Baldur's Gate 3 is a role-playing video game developed and published by Larian Studios. It is the third main game in the Baldur's Gate series, which is based on the Dungeons & Dragons tabletop role-playing system. A partial version of the game was released in early access format for macOS, Windows, and the Stadia streaming service, on 6 October 2020. The game remained in early access until its full release on Windows on 3 August 2023. macOS and PlayStation 5 versions are scheduled for release on 6 September 2023. The Stadia version was cancelled following Stadia's closure.\\nBaldur's Gate 3 was acclaimed by critics, who praised the gameplay, narrative, amount of content, and player choice.\", '\\n=== Critical reception ===\\nThe PC version of Baldur\\'s Gate 3 received \"universal acclaim\" according to review aggregator website Metacritic.Critics praised the quality of the writing, including the detailed depiction of the Dungeons & Dragons setting of Faerûn; the intertwined storylines enabling alternate story moments and a large degree of player freedom; and the characters, particularly companions, that feel interesting and memorable. Critics also praised the production quality, including the voice acting, character and facial animations, and detailed environments. The game\\'s implementation of the Dungeons & Dragons 5th edition ruleset was well received, although some critics were concerned that the complexity of the system could be daunting for players wholly unfamiliar with the tabletop version of the game. The technical performance and its few bugs were highlighted, especially given many vast and complex game systems and interactions. Critics were more lukewarm on the inventory system and some aspects of the user interface, such as needing to micromanage the equipment of multiple characters.Fraser Brown of PC Gamer called the game \"the new pinnacle of the [CRPG] genre\" while IGN Portugal called it a \"must-play masterpiece that sets a new bar in the RPG universe\".Upon its release, the game ignited a debate about the current state of gaming and the gaming industry. Commentators, such as Destin Legarie of IGN, highlighted statements of the developers unaffiliated with Baldur\\'s Gate 3 who were concerned that its success may result in a perceived raising of expectations among the consumers. In response, Legarie asserted the developers should strive to meet and exceed the game\\'s newly set standard in terms of quality and completeness, and that the developers should focus on technical stability, feature completeness and player expectations, rather than focusing on monetisation strategies and releasing unfinished products. Alex Battaglia from Eurogamer praised the game\\'s performance on PC, and added that \"Baldur\\'s Gate 3 launches in a polished state with no game-breaking issues, shaming the technical quality of other big AAA releases\".']\u001b[0m\n",
      "\n",
      "\u001b[1m> Finished chain.\u001b[0m\n"
     ]
    },
    {
     "data": {
      "application/vnd.google.colaboratory.intrinsic+json": {
       "type": "string"
      },
      "text/plain": [
       "\"The gameplay of Baldur's Gate 3 is a role-playing video game that offers both a single-player and cooperative multiplayer element. Players can create one or more characters and form a party along with pre-generated characters to explore the game's story. The game features turn-based combat, similar to Larian's earlier games Divinity: Original Sin and Divinity: Original Sin II, and all combat is based on the D&D 5th Edition rules.\""
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "vector_qa.run(\"What is the gameplay of Baldur's Gate 3 like?\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "OWhJoJq2UCU0"
   },
   "source": [
    "## Summary\n",
    "Leveraging Neo4j's new vector indexing capabilities, you can create a unified data source that powers Retrieval Augmented Generation applications effectively. This allows you to not only implement \"Chat with your PDF or documentation\" solutions but also to conduct real-time analytics, all from a single, robust data source. This multi-purpose utility can streamline your operations and enhances data synergy, making Neo4j a great solution for managing both structured and unstructured data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "id": "k1EJhX4lHOiD"
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "colab": {
   "authorship_tag": "ABX9TyM4Kc3xn8d20DysT9gsKmSI",
   "include_colab_link": true,
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
